package ccm.component.menu;

import ccm.common.Buffer;
import ccm.common.CCMConfigManager;
import ccm.common.Pair;
import ccm.component.override.JButton;
import ccm.component.override.JCheckBox;
import ccm.component.override.JLabel;
import ccm.component.override.JSpinner;
import ccm.component.override.*;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class ArrayMenuJPanel extends MenuItemJPanel implements ActionListener, ExRequestValueInterface
{

    final protected JButton sendJButton;
    final protected ArrayCell[] arrayCells;
    final protected int[] shape;
    boolean needSend;

    protected ArrayMenuJPanel(DelegateSenderInterface sender,String saveArgPrefix,int dataIndex,boolean canModify,ScnsType type,int page,String name,Object ex,Component[] rightMenu)
    {
        super(sender,saveArgPrefix,dataIndex,canModify,type,page,name);
        JPanel topJPanel=new JPanel();
        topJPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        while(this.getComponentCount()!=0)
        {
            Component cp=getComponent(0);
            remove(0);
            topJPanel.add(cp);
        }
        setLayout(new BorderLayout());
        add(topJPanel,BorderLayout.NORTH);
        sendJButton=new JButton("发送");
        topJPanel.add(sendJButton);
        sendJButton.addActionListener(this);
        nameJLabel.setPreferredSize(null);

        JPanel jPanel=new JPanel();
        add(jPanel,BorderLayout.CENTER);
        shape=((int[])ex);
        int total=1;
        for(int a: shape)
        {
            total*=a;
        }
        arrayCells=new ArrayCell[total];
        if(shape.length==1)
        {
            jPanel.setLayout(new GridLayout(2,shape[0]));
            for(int j=0;j<shape[0];++j)
            {
                JLabel jLabel=new JLabel(String.valueOf(j));
                jLabel.setBorder(BorderFactory.createEtchedBorder());
                jPanel.add(jLabel);
            }
            for(int j=0;j<shape[0];++j)
            {
                arrayCells[j]=ArrayCeilFactory.getArrayCeil(type,canModify);
                jPanel.add(arrayCells[j]);
            }
        }
        else if(shape.length==2)
        {
            jPanel.setLayout(new GridLayout(shape[0]+1,shape[1]+1));
            {
                {
                    JLabel jLabel=new JLabel("");
                    jLabel.setBorder(BorderFactory.createEtchedBorder());
                    jPanel.add(jLabel);
                }
                for(int j=0;j<shape[1];++j)
                {
                    JLabel jLabel=new JLabel(String.valueOf(j));
                    jLabel.setBorder(BorderFactory.createEtchedBorder());
                    jPanel.add(jLabel);
                }
            }
            for(int i=0;i<shape[0];++i)
            {
                JLabel jLabel=new JLabel(String.valueOf(i));
                jLabel.setBorder(BorderFactory.createEtchedBorder());
                jPanel.add(jLabel);
                for(int j=0;j<shape[1];++j)
                {
                    arrayCells[i*shape[1]+j]=ArrayCeilFactory.getArrayCeil(type,canModify);
                    jPanel.add(arrayCells[i*shape[1]+j]);
                }
            }
        }
        else
        {
            throw new IllegalArgumentException();
        }
        setSaveArgPrefix(saveArgPrefix);
        if(rightMenu!=null)
        {
            JPopupMenu menu=new JPopupMenu();
            MouseAdapter mouseAdapter=new MouseAdapter()
            {
                public void mouseClicked(MouseEvent e)
                {
                    if(e.getButton()==MouseEvent.BUTTON3)
                    {
                        menu.removeAll();
                        for(Component ri: rightMenu)
                        {
                            menu.add(ri);
                        }
                        menu.add(clipJMenuItem);
                        menu.show((Component)e.getSource(),e.getX(),e.getY());
                    }
                }
            };
            nameJLabel.addMouseListener(mouseAdapter);
            this.addMouseListener(mouseAdapter);
        }
    }

    public static Pair<Object,Integer> decodeInnerDataEx(byte[] receiveBuf,int start)
    {
        int dimSize=Buffer.getUint8(receiveBuf,start);
        start+=1;
        int[] dim=new int[dimSize];
        for(int i=0;i<dimSize;++i)
        {
            dim[i]=Buffer.getUint8(receiveBuf,start);
            start+=1;
        }
        return new Pair<>(dim,start);
    }

    @Override
    public void actionPerformed(ActionEvent e)
    {
        if(e.getSource()==sendJButton)
        {
            needSend=true;
        }
    }

    @Override
    public void setCanModify(boolean canModify)
    {
        super.setCanModify(canModify);
        if(sendJButton!=null)
        {
            sendJButton.setVisible(canModify);
        }
        if(arrayCells!=null)
        {
            for(ArrayCell arrayCell: arrayCells)
            {
                arrayCell.setCanModify(canModify);
            }
        }
    }

    /**
     * 设置参数保存前缀
     * 会自动刷新所有的参数
     *
     * @param saveArgPrefix 参数保存前缀
     */
    public void setSaveArgPrefix(String saveArgPrefix)
    {
        super.setSaveArgPrefix(saveArgPrefix);
        if(arrayCells!=null)
        {
            for(int i=0;i<arrayCells.length;++i)
            {
                arrayCells[i].setSaveArgPrefix(saveArgPrefix+"ccm/component/menu/MenuJPanel/item/"+page+','+name+"/"+i);
            }
        }
    }

    @Override
    public void loadValueToInput()
    {
        for(ArrayCell arrayCell: arrayCells)
        {
            arrayCell.loadValueToInput();
        }
    }

    @Override
    public void requestExValue(UpdateAndSleep updateAndSleep) throws InterruptedException
    {
        for(int round=0;round<arrayCells.length;round+=12)
        {
            System.out.println(dataIndex+" exRequestValue "+round);
            int start=0;
            byte[] buf=new byte[64];
            start=Buffer.setUint16(buf,start,dataIndex);
            start=Buffer.setUint8(buf,start,type.ordinal());
            start=Buffer.setInt32(buf,start,round);
            addTailAndSend(buf,start,0X24);
            updateAndSleep.updateAndSleep();
        }
    }

    @Override
    public void decodeExAndSet(byte[] receiveBuf,int start,int len)
    {
        if(arrayCells!=null)
        {
            System.out.println("decodeExAndSet");
            final int round=Buffer.getInt32(receiveBuf,start);
            final int cnt=(len-4)/type.sizeOf();
            for(int i=0;i<cnt;++i)
            {
                arrayCells[i+round].decodeValueAndSet(receiveBuf,start+4+(i)*type.sizeOf());
            }
            if(cnt+round>=arrayCells.length)
            {
                needRequestValue=false;
            }
        }
    }

    @Override
    public boolean needSendExValue()
    {
        return needSend;
    }

    @Override
    public void sendExValueToDevice(UpdateAndSleep updateAndSleep) throws InterruptedException
    {
        for(int round=0;round<arrayCells.length;round+=12)
        {
            System.out.println(dataIndex+" sendExValue "+round);
            int start=0;
            byte[] buf=new byte[64];
            start=Buffer.setUint16(buf,start,dataIndex);
            start=Buffer.setUint8(buf,start,type.ordinal());
            start=Buffer.setInt32(buf,start,round);
            for(int i=round;i<round+12&&i<arrayCells.length;++i)
            {
                start=arrayCells[i].setBuf(buf,start);
            }
            addTailAndSend(buf,start,0X23);
            if(round+12>=arrayCells.length)
            {
                needSend=false;
                needRequestValue=true;
            }
            updateAndSleep.updateAndSleep();
        }
    }

    private void toString(StringBuilder stringBuilder,int deep,int i)
    {
        if(deep==shape.length)
        {
            stringBuilder.append(arrayCells[i].toString());
        }
        else
        {
            stringBuilder.append('\n').append("    ".repeat(Math.max(0,deep)));
            stringBuilder.append('{');
            for(int j=0;j<shape[deep];++j)
            {
                toString(stringBuilder,deep+1,i*shape[deep]+j);
                stringBuilder.append(',');
            }
            stringBuilder.append('}');
        }
    }

    @Override
    public String toString()
    {
        StringBuilder stringBuilder=new StringBuilder();
        toString(stringBuilder,0,0);
        return stringBuilder.toString();
    }

    @Override
    public void setStep(float step)
    {
        for(ArrayCell arrayCell: arrayCells)
        {
            arrayCell.setStep(step);
        }
    }
}

class ArrayCeilFactory
{
    private ArrayCeilFactory()
    {

    }

    static protected ArrayCell getArrayCeil(ScnsType type,boolean canModify)
    {
        switch(type)
        {
            case SCNS_TYPE_INT8:
                return new Int8ArrayCell(canModify);
            case SCNS_TYPE_INT16:
                return new Int16ArrayCell(canModify);
            case SCNS_TYPE_INT32:
                return new Int32ArrayCell(canModify);
            case SCNS_TYPE_INT64:
                return new Int64ArrayCell(canModify);
            case SCNS_TYPE_UINT8:
                return new Uint8ArrayCell(canModify);
            case SCNS_TYPE_UINT16:
                return new Uint16ArrayCell(canModify);
            case SCNS_TYPE_UINT32:
                return new Uint32ArrayCell(canModify);
            case SCNS_TYPE_UINT64:
                return new Uint64ArrayCell(canModify);
            case SCNS_TYPE_FLOAT:
                return new FloatArrayCell(canModify);
            case SCNS_TYPE_DOUBLE:
                return new FloatArrayCell(canModify);
            case SCNS_TYPE_BOOLEAN:
                return new BooleanArrayCell(canModify);
            default:
                throw new IllegalArgumentException();
        }
    }

}

abstract class ArrayCell extends JPanel
{
    protected boolean canModify;
    protected String saveArgPrefix;

    public ArrayCell(boolean canModify)
    {
        super();
        setLayout(new VFlowLayout());
        setBorder(BorderFactory.createEtchedBorder());
        setCanModify(canModify);
    }

    abstract public void loadConfig();

    abstract protected void storeConfig();

    abstract protected void updateColor();

    public boolean isCanModify()
    {
        return canModify;
    }

    public void setCanModify(boolean canModify)
    {
        this.canModify=canModify;
    }

    /**
     * 设置参数保存前缀
     * 会自动刷新所有的参数
     *
     * @param saveArgPrefix 参数保存前缀
     */
    public void setSaveArgPrefix(String saveArgPrefix)
    {
        this.saveArgPrefix=saveArgPrefix==null?"":(saveArgPrefix.endsWith("/")?saveArgPrefix:(saveArgPrefix+"/"));
        loadConfig();
    }

    abstract public void loadValueToInput();

    abstract protected boolean sameAsValue(Object v);

    abstract public void decodeValueAndSet(byte[] receiveBuf,int i);

    abstract public int setBuf(byte[] buf,int start);

    abstract public String toString();

    public void setStep(float step)
    {
    }
}

class BooleanArrayCell extends ArrayCell
{
    final protected JLabel valueJLabel;
    final protected JCheckBox valueJCheckBox;
    protected Boolean value;

    public BooleanArrayCell(boolean canModify)
    {
        super(canModify);
        valueJLabel=new JLabel();
        valueJLabel.setOpaque(true);
        add(valueJLabel);
        valueJCheckBox=new JCheckBox("true");
        add(valueJCheckBox);
        loadConfig();
        valueJCheckBox.addChangeListener(e->storeConfig());
        valueJCheckBox.addActionListener(e->{
            valueJCheckBox.setText(String.valueOf(valueJCheckBox.isSelected()));
            updateColor();
        });
        setCanModify(canModify);
        Dimension dimension=new Dimension(100,(int)(valueJLabel.getFont().getSize()*1.2+10));
        valueJLabel.setPreferredSize(dimension);
        valueJCheckBox.setPreferredSize(dimension);
        valueJLabel.setBorder(BorderFactory.createEtchedBorder());
        valueJCheckBox.setBorder(BorderFactory.createEtchedBorder());
    }

    @Override
    public void loadConfig()
    {
        if(valueJCheckBox!=null)
        {
            valueJCheckBox.setSelected(CCMConfigManager.setBooleanIfAbsent(saveArgPrefix+"/value",false));
            valueJCheckBox.setText(String.valueOf(valueJCheckBox.isSelected()));
            updateColor();
        }
    }

    @Override
    protected void storeConfig()
    {
        CCMConfigManager.setBoolean(saveArgPrefix+"/value",valueJCheckBox.isSelected());
    }

    @Override
    protected void updateColor()
    {
        if(value!=null)
        {
            valueJLabel.setText(((Boolean)value).toString());
        }
        if(isCanModify()&&!sameAsValue(valueJCheckBox.isSelected()))
        {
            valueJLabel.setForeground(Color.RED);
        }
        else
        {
            valueJLabel.setForeground(null);
        }
    }

    @Override
    public void loadValueToInput()
    {
        valueJCheckBox.setSelected(value);
        updateColor();
        storeConfig();
    }

    @Override
    protected boolean sameAsValue(Object v)
    {
        return ((Boolean)v)==value;
    }

    @Override
    public void decodeValueAndSet(byte[] receiveBuf,int start)
    {
        Boolean value=Buffer.getUint8(receiveBuf,start)!=0;
        if(this.value==null||!sameAsValue(value))
        {
            valueJLabel.setBackground(Color.YELLOW);
        }
        else
        {
            valueJLabel.setBackground(null);
        }
        this.value=value;
        updateColor();
    }

    @Override
    public int setBuf(byte[] buf,int start)
    {
        return Buffer.setUint8(buf,start,valueJCheckBox.isSelected()?1:0);
    }

    @Override
    public String toString()
    {
        return value.toString();
    }
}

abstract class NumberArrayCell extends ArrayCell
{
    final protected JLabel valueJLabel;
    final protected JSpinner valueJSpinner;
    protected Number value;

    public NumberArrayCell(boolean canModify)
    {
        super(canModify);
        setLayout(new VFlowLayout());
        setBorder(BorderFactory.createEtchedBorder());
        valueJLabel=new JLabel();
        valueJLabel.setOpaque(true);
        add(valueJLabel);

        valueJSpinner=new JSpinner(new SpinnerNumberModel());
        add(valueJSpinner);

        loadConfig();
        valueJSpinner.addChangeListener(e->storeConfig());
        valueJSpinner.addChangeListener(e->updateColor());
        setCanModify(canModify);
        Dimension dimension=new Dimension(100,(int)(valueJLabel.getFont().getSize()*1.2+10));
        valueJLabel.setPreferredSize(dimension);
        valueJSpinner.setPreferredSize(dimension);
        valueJLabel.setBorder(BorderFactory.createEtchedBorder());
        valueJSpinner.setBorder(BorderFactory.createEtchedBorder());
    }

    public void loadConfig()
    {
        valueJSpinner.setValue(CCMConfigManager.setNumberIfAbsent(saveArgPrefix+"/value",0));
        //updateColor();
    }

    protected void storeConfig()
    {
        if(saveArgPrefix!=null)
        {
            CCMConfigManager.setNumber(saveArgPrefix+"/value",(Number)valueJSpinner.getValue());
        }
    }

    protected void updateColor()
    {
        if(isCanModify()&&!sameAsValue(valueJSpinner.getValue()))
        {
            valueJLabel.setForeground(Color.RED);
        }
        else
        {
            valueJLabel.setForeground(null);
        }
    }

    public void setCanModify(boolean canModify)
    {
        this.canModify=canModify;
        if(valueJSpinner!=null)
        {
            valueJSpinner.setVisible(canModify);
        }
    }

    public void loadValueToInput()
    {
        valueJSpinner.setValue(value);
        updateColor();
        storeConfig();
    }


    public void setValue(Number value)
    {
        if(value!=null)
        {
            if(this.value==null||!sameAsValue(value))
            {
                valueJLabel.setBackground(Color.YELLOW);
            }
            else
            {
                valueJLabel.setBackground(null);
            }
            this.value=value;
            valueJLabel.setText(((Number)value).toString());
            updateColor();
        }
    }

    @Override
    public String toString()
    {
        return value.toString();
    }

    @Override
    final public void setStep(float step)
    {
        if(step==0)
        {
            return;
        }
        ((SpinnerNumberModel)valueJSpinner.getModel()).setStepSize((step));
    }
}

class Int8ArrayCell extends NumberArrayCell
{
    public Int8ArrayCell(boolean canModify)
    {
        super(canModify);
        Int8MenuItemJPanel.setModel(this.valueJSpinner);
    }

    @Override
    protected boolean sameAsValue(Object v)
    {
        return Int8MenuItemJPanel.sameAsValue(value,v);
    }

    @Override
    public void decodeValueAndSet(byte[] receiveBuf,int start)
    {
        Int8MenuItemJPanel.decodeValueAndSet(this::setValue,receiveBuf,start);
    }

    @Override
    public int setBuf(byte[] buf,int start)
    {
        return Int8MenuItemJPanel.setBuf(buf,start,valueJSpinner);
    }

}

class Int16ArrayCell extends NumberArrayCell
{
    public Int16ArrayCell(boolean canModify)
    {
        super(canModify);
        Int16MenuItemJPanel.setModel(this.valueJSpinner);
    }

    @Override
    protected boolean sameAsValue(Object v)
    {
        return Int16MenuItemJPanel.sameAsValue(value,v);
    }

    @Override
    public void decodeValueAndSet(byte[] receiveBuf,int start)
    {
        Int16MenuItemJPanel.decodeValueAndSet(this::setValue,receiveBuf,start);
    }

    @Override
    public int setBuf(byte[] buf,int start)
    {
        return Int16MenuItemJPanel.setBuf(buf,start,valueJSpinner);
    }

}

class Int32ArrayCell extends NumberArrayCell
{
    public Int32ArrayCell(boolean canModify)
    {
        super(canModify);
        Int32MenuItemJPanel.setModel(this.valueJSpinner);
    }

    @Override
    protected boolean sameAsValue(Object v)
    {
        return Int32MenuItemJPanel.sameAsValue(value,v);
    }

    @Override
    public void decodeValueAndSet(byte[] receiveBuf,int start)
    {
        Int32MenuItemJPanel.decodeValueAndSet(this::setValue,receiveBuf,start);
    }

    @Override
    public int setBuf(byte[] buf,int start)
    {
        return Int32MenuItemJPanel.setBuf(buf,start,valueJSpinner);
    }

}

class Int64ArrayCell extends NumberArrayCell
{
    public Int64ArrayCell(boolean canModify)
    {
        super(canModify);
        Int64MenuItemJPanel.setModel(this.valueJSpinner);
    }

    @Override
    protected boolean sameAsValue(Object v)
    {
        return Int64MenuItemJPanel.sameAsValue(value,v);
    }

    @Override
    public void decodeValueAndSet(byte[] receiveBuf,int start)
    {
        Int64MenuItemJPanel.decodeValueAndSet(this::setValue,receiveBuf,start);
    }

    @Override
    public int setBuf(byte[] buf,int start)
    {
        return Int64MenuItemJPanel.setBuf(buf,start,valueJSpinner);
    }

}


class Uint8ArrayCell extends NumberArrayCell
{
    public Uint8ArrayCell(boolean canModify)
    {
        super(canModify);
        Uint8MenuItemJPanel.setModel(this.valueJSpinner);
    }

    @Override
    protected boolean sameAsValue(Object v)
    {
        return Uint8MenuItemJPanel.sameAsValue(value,v);
    }

    @Override
    public void decodeValueAndSet(byte[] receiveBuf,int start)
    {
        Uint8MenuItemJPanel.decodeValueAndSet(this::setValue,receiveBuf,start);
    }

    @Override
    public int setBuf(byte[] buf,int start)
    {
        return Uint8MenuItemJPanel.setBuf(buf,start,valueJSpinner);
    }

}


class Uint16ArrayCell extends NumberArrayCell
{
    public Uint16ArrayCell(boolean canModify)
    {
        super(canModify);
        Uint16MenuItemJPanel.setModel(this.valueJSpinner);
    }

    @Override
    protected boolean sameAsValue(Object v)
    {
        return Uint16MenuItemJPanel.sameAsValue(value,v);
    }

    @Override
    public void decodeValueAndSet(byte[] receiveBuf,int start)
    {
        Uint16MenuItemJPanel.decodeValueAndSet(this::setValue,receiveBuf,start);
    }

    @Override
    public int setBuf(byte[] buf,int start)
    {
        return Uint16MenuItemJPanel.setBuf(buf,start,valueJSpinner);
    }

}

class Uint32ArrayCell extends NumberArrayCell
{
    public Uint32ArrayCell(boolean canModify)
    {
        super(canModify);
        Uint32MenuItemJPanel.setModel(this.valueJSpinner);
    }

    @Override
    protected boolean sameAsValue(Object v)
    {
        return Uint32MenuItemJPanel.sameAsValue(value,v);
    }

    @Override
    public void decodeValueAndSet(byte[] receiveBuf,int start)
    {
        Uint32MenuItemJPanel.decodeValueAndSet(this::setValue,receiveBuf,start);
    }

    @Override
    public int setBuf(byte[] buf,int start)
    {
        return Uint32MenuItemJPanel.setBuf(buf,start,valueJSpinner);
    }

}

class Uint64ArrayCell extends NumberArrayCell
{
    public Uint64ArrayCell(boolean canModify)
    {
        super(canModify);
        Uint64MenuItemJPanel.setModel(this.valueJSpinner);
    }

    @Override
    protected boolean sameAsValue(Object v)
    {
        return Uint64MenuItemJPanel.sameAsValue(value,v);
    }

    @Override
    public void decodeValueAndSet(byte[] receiveBuf,int start)
    {
        Uint64MenuItemJPanel.decodeValueAndSet(this::setValue,receiveBuf,start);
    }

    @Override
    public int setBuf(byte[] buf,int start)
    {
        return Uint64MenuItemJPanel.setBuf(buf,start,valueJSpinner);
    }

}


class FloatArrayCell extends NumberArrayCell
{
    public FloatArrayCell(boolean canModify)
    {
        super(canModify);
        FloatMenuItemJPanel.setModel(this.valueJSpinner);
    }

    @Override
    protected boolean sameAsValue(Object v)
    {
        return FloatMenuItemJPanel.sameAsValue(value,v);
    }

    @Override
    public void decodeValueAndSet(byte[] receiveBuf,int start)
    {
        FloatMenuItemJPanel.decodeValueAndSet(this::setValue,receiveBuf,start);
    }

    @Override
    public int setBuf(byte[] buf,int start)
    {
        return FloatMenuItemJPanel.setBuf(buf,start,valueJSpinner);
    }

}

class DoubleArrayCell extends NumberArrayCell
{
    public DoubleArrayCell(boolean canModify)
    {
        super(canModify);
        DoubleMenuItemJPanel.setModel(this.valueJSpinner);
    }

    @Override
    protected boolean sameAsValue(Object v)
    {
        return DoubleMenuItemJPanel.sameAsValue(value,v);
    }

    @Override
    public void decodeValueAndSet(byte[] receiveBuf,int start)
    {
        DoubleMenuItemJPanel.decodeValueAndSet(this::setValue,receiveBuf,start);
    }

    @Override
    public int setBuf(byte[] buf,int start)
    {
        return DoubleMenuItemJPanel.setBuf(buf,start,valueJSpinner);
    }

}